import { DEBUG } from "cc/env";
import { Macro } from "../Main/Macros";
import c3d from "../tool/cc";
import { BundleManager } from "./BundleManager";
import { CacheManager } from "./CacheManager";
import { HotupdateManager } from "./hotupdate/HotupdateManager";
import { Resource } from "./Resource";



export class _AssetManager {
    private logTag = `[AssetManager]: `;
    cache = new CacheManager();
    bundl = new BundleManager();
    hotupdate = new HotupdateManager();

    /**
     * 获取Bundle
     * @param bundle Bundle名|Bundle
     */
    getBundle(bundle: BUNDLE_TYPE) {
        return this.bundl.getBundle(bundle);
    }

    /**
     * 加载资源子包
     * @param bundle 包名
     * @param path 地址
     * @param type 类型
     * @param onProgress 加载时回调
     * @param onComplete 完成时回调
     * @returns 
     */
    load(
        bundle: BUNDLE_TYPE,
        path: string,
        type: typeof c3d.Asset,
        onProgress: (finish: number, total: number, item: c3d.AssetManager.RequestItem) => void,
        onComplete: (data: Resource.CacheData) => void
    ): void {
        DEBUG && c3d.log(`load bundle : ${bundle} path : ${path}`);

        let cache = this.cache.get(bundle, path);
        if (cache) {
            //存在缓存信息
            this.dealResCache(cache, path, onComplete);
        } else {
            //无缓存信息,立即创建资源缓存
            cache = new Resource.CacheData({ path, type, bundle });
            this.cache.set(bundle, path, cache);
            console.time(`加载资源 : ${cache.info.url}`);

            let _bundle = this.getBundle(bundle);
            if (!_bundle) {
                //如果bundle不存在
                this._onLoadComplete(cache, onComplete, new Error(`${this.logTag} ${bundle} 没有加载，请先加载`), null);
            } else {
                let res = _bundle.get(path, type);
                if (res) {
                    this._onLoadComplete(cache, onComplete, null, res);
                } else {
                    if (onProgress) {
                        _bundle.load(path, type, onProgress, this._onLoadComplete.bind(this, cache, onComplete));
                    } else {
                        _bundle.load(path, type, this._onLoadComplete.bind(this, cache, onComplete));
                    }
                }
            }
        }
    }

    /**
     * 加载一个目录下的资源
     * @param bundle 资源包类型
     * @param path 资源path
     * @param type 资源类型
     * @param onProgress 加载回调
     * @param onComplete 完成回调
     * @returns 
     */
    loadDir(
        bundle: BUNDLE_TYPE,
        path: string,
        type: typeof c3d.Asset,
        onProgress: (finish: number, total: number, item: c3d.AssetManager.RequestItem) => void,
        onComplete: (data: Resource.CacheData) => void
    ): void {
        if (DEBUG) {
            c3d.log(`load bundle : ${bundle} path : ${path}`)
        }
        let cache = this.cache.get(bundle, path);
        if (cache) {
            this.dealResCache(cache, path, onComplete);
        } else {
            //无缓存信息
            cache = new Resource.CacheData({ path, type, bundle });
            this.cache.set(bundle, path, cache);
            console.time(`加载资源 : ${cache.info.url}`);

            let _bundle = this.getBundle(bundle);
            if (!_bundle) {
                //如果bundle不存在
                this._onLoadComplete(cache, onComplete, new Error(`${this.logTag} ${bundle} 没有加载，请先加载`), null);
            } else {
                if (onProgress) {
                    _bundle.loadDir(path, type, onProgress, this._onLoadComplete.bind(this, cache, onComplete));
                } else {
                    _bundle.loadDir(path, type, this._onLoadComplete.bind(this, cache, onComplete));
                }
            }
        }
    }

    /**
     * 处理已经加载或正在加载的资源，即不再进行加载
     * @param cache 缓存资源
     * @param path 资源path
     * @param onComplete 完成回调
     */
    private dealResCache(cache: Resource.CacheData, path: string, onComplete: (data: Resource.CacheData) => void) {
        if (cache.isLoaded) {
            //已经加载完成
            if (DEBUG && cache.status == Resource.CacheStatus.WAITTING_FOR_RELEASE) {
                c3d.warn(this.logTag, `资源:${path} 等待释放，但资源已经加载完成，此时有人又重新加载，不进行释放处理`);
            }
            onComplete(cache);
        } else {
            if (DEBUG && cache.status == Resource.CacheStatus.WAITTING_FOR_RELEASE) {
                c3d.warn(this.logTag, `资源:${path}等待释放，但资源处理加载过程中，此时有人又重新加载，不进行释放处理`);
            }
            cache.finishCb.push(onComplete);
        }
        //重新复位资源状态
        cache.status = Resource.CacheStatus.NONE;
    }

    /**
     * 处理资源加载后的引用和释放
     * @param cache 资源缓存
     * @param completeCallback 完成回调
     * @param err 错误
     * @param data 数据
     */
    private _onLoadComplete(cache: Resource.CacheData, completeCallback: (data: Resource.CacheData) => void, err: Error | null, data: c3d.Asset | c3d.Asset[] | null) {
        cache.isLoaded = true;
        //添加引用关系
        let tempCache = cache;
        if (err) {
            c3d.error(`${this.logTag}加载资源失败:${cache.info.url} 原因:${err.message ? err.message : "未知"}`);
            cache.data = null;
            tempCache.data = null;
            this.cache.remove(cache.info.bundle, cache.info.url);
            completeCallback(cache);
        } else {
            DEBUG && c3d.log(`${this.logTag}加载资源成功:${cache.info.url}`);
            cache.data = data;
            tempCache.data = data;
            completeCallback(cache);
        }

        //加载过程，有不同地方调用过来加载同一个资源的地方，都回调回去
        cache.doFinish(tempCache);
        cache.doGet(tempCache.data);

        if (cache.status == Resource.CacheStatus.WAITTING_FOR_RELEASE) {
            DEBUG && c3d.warn(this.logTag, `资源:${cache.info.url}加载完成，但缓存状态为等待销毁，销毁资源`);
            if (cache.data) {
                cache.status = Resource.CacheStatus.NONE;
                let info = new Resource.Info;
                info.url = cache.info.url;
                info.type = cache.info.type;
                info.data = cache.data;
                info.bundle = cache.info.bundle;
                this.releaseAsset(info);
            }
        }

        console.timeEnd(`加载资源 : ${cache.info.url}`);
    }

    loadImage(url: string, isNeedCache: boolean) {
        let self = this;
        return new Promise<c3d.SpriteFrame | null>((resolve) => {
            if (url == null || url == undefined || url.length <= 0) {
                resolve(null);
                return;
            }
            if (isNeedCache) {
                //如果存在缓存 ，直接取出
                let spCache = GM.assetMgr.cache.remoteCaches.getSpriteFrame(url);
                if (spCache && spCache.data) {
                    if (DEBUG) c3d.log(`从缓存精灵帧中获取:${url}`);
                    resolve(<c3d.SpriteFrame>(spCache.data));
                    return;
                } else {
                    //错误处理
                    if (DEBUG) c3d.log(`错误资源，删除缓存信息，重新加载:${url}`);
                    GM.assetMgr.cache.remoteCaches.remove(url);
                }
            } else {
                //不需要缓存，先删除之前的,再重新加载
                if (DEBUG) c3d.log(`不需要缓存信息，删除缓存，重新加载${url}`);
                GM.assetMgr.cache.remoteCaches.remove(url);
            }
            self._loadRemoteRes(url, c3d.Texture2D, isNeedCache).then((data: any) => {
                //改变缓存类型
                let cache = GM.assetMgr.cache.remoteCaches.get(url);
                if (data && cache) {
                    if (DEBUG) c3d.log(`加载图片完成${url}`);
                    cache.data = data;
                    (<c3d.Asset>cache.data).name = url;
                    let spriteFrame = GM.assetMgr.cache.remoteCaches.setSpriteFrame(url, cache.data);
                    resolve(spriteFrame);
                } else {
                    if (DEBUG) c3d.warn(`加载图片错误${url}`);
                    resolve(null);
                }
            })
        });
    }

    /**
     * 加载骨骼资源
     * @param path 资源path
     * @param name 名字
     * @param isNeedCache 是否需要缓存
     * @returns 
     */
    loadSkeleton(path: string, name: string, isNeedCache: boolean) {
        let self = this;
        return new Promise<c3d.sp.SkeletonData | null>((resolve) => {
            if (path && name) {
                let url = `${path}/${name}`;
                let spineAtlas = `${path}/${name}.atlas`, spinePng = `${path}/${name}.png`, spineJson = `${path}/${name}.json`;
                let cache = GM.assetMgr.cache.remoteCaches.get(url);
                if (cache) {
                    if (cache.isLoaded) {
                        resolve(<c3d.sp.SkeletonData>(cache.data));
                    } else {
                        cache.finishCb.push(resolve);
                    }
                } else {
                    cache = new Resource.CacheData({ type: c3d.sp.SkeletonData, bundle: Macro.BUNDLE_REMOTE, resourceType: Resource.Type.Remote });
                    GM.assetMgr.cache.remoteCaches.set(url, cache);
                    self._loadRemoteRes(spinePng, c3d.Asset, isNeedCache).then((image: c3d.ImageAsset) => {
                        if (!image) {
                            resolve(null);
                            cache = cache as Resource.CacheData;
                            cache.doFinish(null);
                            GM.assetMgr.cache.remoteCaches.remove(url);
                        } else return { image };
                    }).then(async (obj) => {
                        if (!obj) return;

                        const json = await self._loadRemoteRes(spineJson, c3d.JsonAsset, isNeedCache);
                        if (!json) {
                            resolve(null);
                            cache = cache as Resource.CacheData;
                            cache.doFinish(null);
                            GM.assetMgr.cache.remoteCaches.remove(url);
                        } else return { json, ...obj };
                    }).then(async (obj) => {
                        if (!obj) return;

                        const atlas = await self._loadRemoteRes(spineAtlas, c3d.TextAsset, isNeedCache);
                        if (!atlas) {
                            resolve(null);
                            cache = cache as Resource.CacheData;
                            cache.doFinish(null);
                            GM.assetMgr.cache.remoteCaches.remove(url);
                        } else return { atlas, ...obj };
                    }).then((obj) => {
                        if (!obj) return;

                        //生成SkeletonData数据
                        let asset = new c3d.sp.SkeletonData;
                        asset.skeletonJson = obj.json.json;
                        asset.atlasText = obj.atlas.text;

                        let texture = new c3d.Texture2D();
                        texture.image = obj.image;
                        asset.textures = [texture];

                        let pngName = name + ".png"
                        asset.textureNames = [pngName];
                        cache = cache as Resource.CacheData;
                        cache.info.url = url;
                        asset.name = url;
                        cache.data = asset;
                        cache.isLoaded = true;
                        resolve(<c3d.sp.SkeletonData>(cache.data));
                        cache.doFinish(cache.data);
                    })
                }
            } else {
                resolve(null);
            }
        });
    }

    /**
     * 加载远程资源
     * @param url 资源url
     * @param type 类型
     * @param isNeedCache 是否需要缓存
     * @returns 
     */
    private _loadRemoteRes<T extends typeof c3d.Asset>(url: string, type: T, isNeedCache: boolean) {
        return new Promise<InstanceType<T>>((resolve) => {
            let cache = GM.assetMgr.cache.remoteCaches.get(url);
            if (cache) {
                //有缓存,查看是否已经加载,如果已经有，就加载完成
                if (cache.isLoaded) {
                    resolve(cache.data as any);
                } else { //正在加载中
                    cache.finishCb.push(resolve);
                }
            } else {
                //没有缓存存在,生成加载缓存
                cache = new Resource.CacheData({ type, resourceType: Resource.Type.Remote });
                GM.assetMgr.cache.remoteCaches.set(url, cache);
                c3d.assetManager.loadRemote(url, { cacheAsset: true, reloadAsset: !isNeedCache }, (error, data) => {
                    if (cache) {
                        cache.isLoaded = true;
                        if (data) {
                            cache.data = data;
                            cache.data.addRef();
                            if (DEBUG) c3d.log(`加载远程资源完成:${url}`);
                        } else if (DEBUG)
                            c3d.warn(`加载本地资源异常:${url}`);

                        //把再加载过程里，双加载同一资源的回调都回调回去
                        cache.doFinish(data);
                        resolve(cache.data as any)
                    }
                })
            }
        });
    }


    /**
     * 释放资源
     * @param info 
     * @returns 
     */
    releaseAsset(info: Resource.Info) {
        if (info && info.bundle) {
            let cache = this.cache.get(info.bundle, info.url, false);
            if (!cache) {
                return;
            } else if (cache.isInvalid) {
                DEBUG && c3d.warn(`资源已经释放 url : ${info.url}`);
                return;
            }

            if (cache.isLoaded) {
                if (cache.info.retain) {
                    // 常驻资源不释放
                    DEBUG && c3d.log(`常驻资源 url : ${cache.info.url}`);
                    return;
                }
                DEBUG && c3d.log(`释放资源 : ${info.bundle}.${info.url}`);
                let isRemoveInfo = this.cache.removeWithInfo(info);
                if (isRemoveInfo) {
                    //根据资源类型释放
                    let bundle = this.getBundle(info.bundle);
                    if (bundle) {
                        if (Array.isArray(info.data)) {
                            for (let i = 0; i < info.data.length; i++) {
                                let path = `${info.url}/${info.data[i].name}`;
                                bundle.release(path, info.type);
                            }
                            DEBUG && c3d.log(`成功释放资源目录 : ${info.bundle}.${info.url}`);
                        } else {
                            bundle.release(info.url, info.type);
                            DEBUG && c3d.log(`成功释放资源 : ${info.bundle}.${info.url}`);
                        }
                    } else {
                        c3d.error(`${info.bundle} no found`);
                    }
                } else if (DEBUG) {
                    //资源有被引用的，输出被引用的资源log
                    if (Array.isArray(info.data)) {
                        for (let i = 0; i < info.data.length; i++) {
                            if (info.data[i].refCount > 0) {
                                c3d.warn(`资源bundle : ${info.bundle} url : ${info.url}/${info.data[i].name} 被其它界面引用 refCount : ${info.data[i].refCount}`)
                            }
                        }
                    } else {
                        c3d.warn(`资源bundle : ${info.bundle} url : ${info.url} 被其它界面引用 refCount : ${info.data.refCount}`)
                    }
                }
            } else {
                cache.status = Resource.CacheStatus.WAITTING_FOR_RELEASE;
                DEBUG && c3d.warn(`${cache.info.url} 正在加载，等待加载完成后进行释放`);
            }

        }
    }

    /**
     * 添加常驻资源
     * @param info 资源info
     */
    retainAsset(info: Resource.Info) {
        if (info) {
            let cache = this.cache.get(info.bundle, info.url)
            if (cache) {
                if (DEBUG && info.data != cache.data) {
                    c3d.error(`错误的retainAsset :${info.url}`);
                }
                if (!cache.info.retain) {
                    cache.info.retain = info.retain;
                }
                if (Array.isArray(cache.data)) {
                    for (let i = 0; i < cache.data.length; i++) {
                        cache.data[i] && cache.data[i].addRef();
                    }
                } else {
                    cache.data && cache.data.addRef();
                }
            } else {
                if (DEBUG) c3d.error(`retainAsset cache.data is null`);
            }
        } else {
            if (DEBUG) c3d.error(`retainAsset info is null`);
        }
    }

    /**
     * 添加常驻资源
     * @param prefab 
     */
    addPersistAsset(url: string, data: c3d.Asset, bundle: BUNDLE_TYPE) {
        let info = new Resource.Info;
        info.url = url;
        info.data = data;
        info.bundle = bundle;
        info.retain = true;
        this.retainAsset(info);
    }


    /**由主游戏控制器驱动，在下载远程资源时，设置一个上限下载任务数据，以免同一时间任务数量过大 */
    update() {

    }
}